package com.csaba.db;

import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.WorkbenchException;

import com.csaba.db.connection.DBConsumer;
import com.csaba.util.LoggerHelper;

public class DatabaseManager
{
	private final static Logger logger = LoggerHelper.getDefaultLogger();

	private static DatabaseManager instance;

	public static DatabaseManager getInstance()
	{
		if ( instance == null )
		{
			//initialize JDBC driver
			try
			{
				Class.forName("org.h2.Driver");
			}
			catch ( final ClassNotFoundException ex )
			{
				logger.log(Level.SEVERE, "Unable to initialize JDBC driver.", ex);
			}

			instance = new DatabaseManager();
		}
		return instance;
	}

	//fields

	private IProject currentProject;
	private String dbUrl;
	private String currentPassword;

	private DatabaseManager()
	{
		//do nothing
	}

	private void closeAllConnections( final IProgressMonitor monitor ) throws CoreException
	{
		final IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		final IProject[] projects = root.getProjects();
		monitor.beginTask("Closing all open projects.", projects.length);

		for ( final IProject project : projects )
		{

			if ( project.isOpen() )
			{
				monitor.worked(1);

				project.close(monitor);
			}
		}
	}

	public void closeAllConnections()
	{

		final IRunnableWithProgress op = new IRunnableWithProgress()
		{
			public void run( final IProgressMonitor monitor ) throws InvocationTargetException
			{
				try
				{
					closeActiveDatabase(monitor);

					closeAllConnections(monitor);
				}
				catch ( final CoreException e )
				{
					throw new InvocationTargetException(e);
				}
				finally
				{
					monitor.done();
				}
			}

		};

		final Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
		try
		{
			new ProgressMonitorDialog(shell).run(true, true, op);
		}
		catch ( final InterruptedException e )
		{
			logger.fine("Project closing is interrupted: " + e);
		}
		catch ( final InvocationTargetException e )
		{
			final Throwable realException = e.getTargetException();
			MessageDialog.openError(shell, "Error", "Exception occured: " + realException.getMessage());
			logger.log(Level.SEVERE, "Error closing open database.", realException);
		}

	}

	private static String generateURL( final String name )
	{
		return "jdbc:h2:" + name + "/csabadb;CIPHER=AES";
	}

	public void initializeDB( final IProject project, final String password ) throws CoreException
	{
		currentProject = project;
		currentPassword = password;
		if ( currentProject == null || !currentProject.exists() )
		{
			Activator.throwCoreException("Database cannot be created as the project doesn't exists: " + project);
		}

		Connection conn = null;
		try
		{
			dbUrl = generateURL(currentProject.getLocation().toString());
			logger.fine("Creating database on the following url: " + dbUrl);
			conn = DriverManager.getConnection(dbUrl, "sa", currentPassword + " ");

			//FIXME initialization of database is required in order store versions of plugins
		}
		catch ( final SQLException ex )
		{
			Activator.throwCoreException("Failed to initialize the database.", ex);
		}
		finally
		{
			if ( conn != null )
				try
				{
					conn.close();
				}
				catch ( final SQLException ex )
				{
					logger.log(Level.SEVERE, "Error closing database connection.", ex);
				}
		}

	}

	//TODO connection pooling must be implemented: the same connection could be returned to the same consumer
	// if there is active connection for the requester.
	public Connection getConnection( final DBConsumer consumer ) throws ClassNotFoundException, SQLException
	{
		final Connection conn = DriverManager.getConnection(dbUrl, "sa", currentPassword + " ");

		//FIXME verify that the database is correctly initialized for the DBConsumer version
		return conn;
	}

	public boolean hasActiveDatabase()
	{
		return currentProject != null;
	}

	public void closeActiveDatabase( final IProgressMonitor monitor ) throws CoreException
	{
		// FIXME close active datbase
		// FIXME check if there is open connection
		// FIXME warn customer about that connection cannot be closed.

		//BEWARE: it may happen that the password or the URL is invalid
		currentPassword = null;
		dbUrl = null;

		//close project
		if ( currentProject != null && currentProject.isOpen() )
			currentProject.close(monitor);

		currentProject = null;

	}

	public void openDatabase( final String name, final String password, final IProgressMonitor monitor )
			throws CoreException
	{
		if ( currentProject != null )
			closeActiveDatabase(monitor);

		final IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		final IProject[] projects = root.getProjects();
		if ( monitor != null )
			monitor.beginTask("Adatbázis megnyitás", 1);

		for ( final IProject project : projects )
		{

			if ( project.getName().equals(name) )
			{
				if ( monitor != null )
					monitor.worked(1);

				project.open(monitor);
				currentProject = project;
			}
		}

		if ( currentProject == null )
			throw new WorkbenchException("A " + name + " adatbázis nem található.");

		dbUrl = generateURL(currentProject.getLocation().toString());
		currentPassword = password;

		if ( monitor != null )
			monitor.beginTask("Adatbázis kapcsolat tesztelése", 1);

		try
		{
			final Connection connect = getConnection(null);
			connect.close();
		}
		catch ( final Exception ex )
		{
			try
			{
				closeActiveDatabase(null);
			}
			catch ( final Exception e )
			{
				//do nothing to avoid hiding original exception
			}
			final IStatus status = new Status(IStatus.ERROR, Activator.PLUGIN_ID,
					"Nem sikerült az adatbázis kapcsolatot megnyitni.\n"
							+ "Ellenőrizze le, hogy helyesen adta-e meg az adatbázis jelszót.", ex);
			throw new WorkbenchException(status);
		}

		if ( monitor != null )
			monitor.worked(1);
	}
}
